前面我們介紹了 isolation level 的隱式鎖與顯示鎖,
這些都是確確實實的將資料加上鎖,
當資料被上鎖後,必須等待前者的鎖釋放後才能取用,
也就是俗稱的悲觀鎖。
有悲觀鎖,那麼有樂觀鎖嗎?
有多樂觀?樂觀能解決問題?
還真的能,而且可以做很多事情
在介紹樂觀鎖之前,我們先看一下一般 table
以下是一個庫存表。
我們先使用悲觀鎖(顯式鎖)的方式來更新數量,
當我們要更新庫存時,我們要先查詢目前數量是多少,並且讓其他 tx 不能讀不能改該筆庫存紀錄,
所以 select ... for update where id=1
來取出目前庫存,並且上鎖
然後使用update ...
來將 num-1,
最後在查詢一下目前更新後的數量,將更新結果回傳給使用者。
可能會這樣寫
begin
# for update 讓其他tx不可讀寫
select num from products where id=1 for update;
# 更新數量
update products set num=num-1 where id=1;
# 查詢最後剩餘多少
select num from products
commit
這麼樣其他 tx 在 select 時就會 block 住了(轉圈圈),
直到第一個 tx 釋放鎖後開始執行。
實作樂觀鎖主要分成兩類:
Two widely known algorithms of optimistic concurrency control are:
我們這個範例,採取 version 方案。
我們需要改一下 table,加上 version 的欄位
樂觀鎖的整體關鍵就是在於 version 這個欄位,
查詢目前數量時,也帶上 version,告知接下來的 update 要更新 version 為 0 的那筆紀錄,
更新時,更新數量且更新 version,
下個 tx 如果還拿 version0 來更新時,
就不會更新到目前已經更新成 version1 的那筆資料了。
begin
select 'num','version' from products
where id=1 and version=0;
update products set num=num-1, version=version+1
where id=1 and version=0;
select num from products
commit
有沒有發現過程中完全沒上任何鎖,
透過 version 來做 condition write。
我們看一下他是如何達成目標成果的。
主要 tx 在 select 時,透過 version 的方式做控管,更新時也是依照 version 更新,
update 後,可以看到變動的 rows 數目,就可以得知這次的更改是否成功。
修改成功: select 時的 version 和 update 時的 version 相同,就可以成功更新,可以發現變動的 rows=1
修改失敗: select 時的 version 和 update 時的 version 不相同,更新不到,rows=0。
其他 tx 同理。
我們可以透過變動的 rows=1 來確定是否修改成功,
但當發現 rows=0 時,我們可以透過 backoff retry 的方式在程式之中幫忙將 num-1 這個動作修改做到好
即發現 update rows=0 時,再重新 select version,update...,直到 update rows=1 為止。
優點:
缺點:
對於 db 演進的發展樂觀鎖是好的方案嗎?
要清楚這個問題,先拉回來我們原先認識的使用 SXlock db
完了丸了八比 Q 了
當我們在使用 讀鎖 (share lock) 時,會禁止其他 tx 做 update
而做作 寫鎖 (mutex lock) 時,會禁止其他 tx 做 select
對,你沒看錯 SXlock db,做 update 操作時,其他的 select 會被 block 住。
而且,當 commit 的時間點越後面,卡住的 tx 就會越多,更容易發生 deadlock 或 timeout 問題。
Timeout & rollack (deadlock detection),Wait-die (deadlock prevention)這兩大主題將放在下集做更詳細的介紹
但事情真的是這樣嗎?
為什麽 tx1 在 update 時,tx2 仍然可以讀取?
只有兩種可能
類似剛剛的樂觀鎖,在資料表上加上 version 欄位,
每次的 insert/update 都會額外增加版本
MVCC 的資料庫的 Record 只有 X lock ,而沒有 S lock 的
MVCC 在讀取時,會讀最該 Record 「最新」的 Committed 版本(當前讀),所以自然地沒有了 Dirty Read
因為讀不會被阻擋,所以只有 write-write conflict
接下來將會介紹 Postgres 與 Mysql 實作的 MVCC
MVCC is type of Optimistic Lock? Yes!
每次的 insert/update 都會額外增加版本
這點講的真的很好, 所以MVCC上才需要一直清除過往歷史版本.
很適合讀取遠超過修改的比例時的場景, 提高讀取的吞吐量
悲觀鎖, 在修改比例特高時, 反而能透過互斥鎖降低衝突發生的機率